package itsm.isperp.workflow.engine.diagram;

import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.activiti.bpmn.model.Activity;
import org.activiti.bpmn.model.Artifact;
import org.activiti.bpmn.model.Association;
import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BoundaryEvent;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.BusinessRuleTask;
import org.activiti.bpmn.model.CallActivity;
import org.activiti.bpmn.model.EndEvent;
import org.activiti.bpmn.model.ErrorEventDefinition;
import org.activiti.bpmn.model.Event;
import org.activiti.bpmn.model.EventDefinition;
import org.activiti.bpmn.model.EventGateway;
import org.activiti.bpmn.model.EventSubProcess;
import org.activiti.bpmn.model.ExclusiveGateway;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowElementsContainer;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.Gateway;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.InclusiveGateway;
import org.activiti.bpmn.model.IntermediateCatchEvent;
import org.activiti.bpmn.model.Lane;
import org.activiti.bpmn.model.ManualTask;
import org.activiti.bpmn.model.MultiInstanceLoopCharacteristics;
import org.activiti.bpmn.model.ParallelGateway;
import org.activiti.bpmn.model.Pool;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.ReceiveTask;
import org.activiti.bpmn.model.ScriptTask;
import org.activiti.bpmn.model.SendTask;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.bpmn.model.SignalEventDefinition;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.bpmn.model.SubProcess;
import org.activiti.bpmn.model.Task;
import org.activiti.bpmn.model.TextAnnotation;
import org.activiti.bpmn.model.ThrowEvent;
import org.activiti.bpmn.model.TimerEventDefinition;
import org.activiti.bpmn.model.UserTask;

public class CustomerProcessDiagramGenerator {

	public CustomerProcessDiagramGenerator() {
		this(1.0);
	}

	public CustomerProcessDiagramGenerator(final double scaleFactor) {
		// start event
		activityDrawInstructions.put(StartEvent.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						StartEvent startEvent = (StartEvent) flowNode;
						if (startEvent.getEventDefinitions() != null
								&& startEvent.getEventDefinitions().size() > 0) {
							EventDefinition eventDefinition = startEvent
									.getEventDefinitions().get(0);
							if (eventDefinition instanceof TimerEventDefinition) {
								processDiagramCanvas.drawTimerStartEvent(
										graphicInfo, scaleFactor);
							} else if (eventDefinition instanceof ErrorEventDefinition) {
								processDiagramCanvas.drawErrorStartEvent(
										graphicInfo, scaleFactor);
							} else if (eventDefinition instanceof SignalEventDefinition) {
								processDiagramCanvas.drawSignalStartEvent(
										graphicInfo, scaleFactor);
							}
						} else {
							processDiagramCanvas.drawNoneStartEvent(
									flowNode.getName(), graphicInfo);
						}
					}
				});

		// signal catch
		activityDrawInstructions.put(IntermediateCatchEvent.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode;
						if (intermediateCatchEvent.getEventDefinitions() != null
								&& intermediateCatchEvent.getEventDefinitions()
										.size() > 0) {
							if (intermediateCatchEvent.getEventDefinitions()
									.get(0) instanceof SignalEventDefinition) {
								processDiagramCanvas.drawCatchingSignalEvent(
										flowNode.getName(), graphicInfo, true,
										scaleFactor);
							} else if (intermediateCatchEvent
									.getEventDefinitions().get(0) instanceof TimerEventDefinition) {
								processDiagramCanvas.drawCatchingTimerEvent(
										flowNode.getName(), graphicInfo, true,
										scaleFactor);
							}
						}
					}
				});

		// signal throw
		activityDrawInstructions.put(ThrowEvent.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						ThrowEvent throwEvent = (ThrowEvent) flowNode;
						if (throwEvent.getEventDefinitions() != null
								&& throwEvent.getEventDefinitions().size() > 0) {
							if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) {
								processDiagramCanvas.drawThrowingSignalEvent(
										graphicInfo, scaleFactor);
							}
						} else {
							processDiagramCanvas.drawThrowingNoneEvent(
									graphicInfo, scaleFactor);
						}
					}
				});

		// end event
		activityDrawInstructions.put(EndEvent.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						EndEvent endEvent = (EndEvent) flowNode;
						if (endEvent.getEventDefinitions() != null
								&& endEvent.getEventDefinitions().size() > 0) {
							if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) {
								processDiagramCanvas.drawErrorEndEvent(
										flowNode.getName(), graphicInfo,
										scaleFactor);
							}
						} else {
							processDiagramCanvas.drawNoneEndEvent(
									flowNode.getName(), graphicInfo,
									scaleFactor);
						}
					}
				});

		// task
		activityDrawInstructions.put(Task.class, new ActivityDrawInstruction() {

			public void draw(CustomerProcessDiagramCanvas processDiagramCanvas,
					BpmnModel bpmnModel, FlowNode flowNode) {
				GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode
						.getId());
				processDiagramCanvas.drawTask(flowNode.getName(), graphicInfo);
			}
		});

		// user task
		activityDrawInstructions.put(UserTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawUserTask(flowNode.getName(),
								graphicInfo, scaleFactor);
					}
				});

		// script task
		activityDrawInstructions.put(ScriptTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawScriptTask(flowNode.getName(),
								graphicInfo, scaleFactor);
					}
				});

		// service task
		activityDrawInstructions.put(ServiceTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						ServiceTask serviceTask = (ServiceTask) flowNode;
						if ("camel".equalsIgnoreCase(serviceTask.getType())) {
							processDiagramCanvas.drawCamelTask(
									serviceTask.getName(), graphicInfo,
									scaleFactor);
						} else if ("mule".equalsIgnoreCase(serviceTask
								.getType())) {
							processDiagramCanvas.drawMuleTask(
									serviceTask.getName(), graphicInfo,
									scaleFactor);
						} else {
							processDiagramCanvas.drawServiceTask(
									serviceTask.getName(), graphicInfo,
									scaleFactor);
						}
					}
				});

		// receive task
		activityDrawInstructions.put(ReceiveTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawReceiveTask(
								flowNode.getName(), graphicInfo, scaleFactor);
					}
				});

		// send task
		activityDrawInstructions.put(SendTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawSendTask(flowNode.getName(),
								graphicInfo, scaleFactor);
					}
				});

		// manual task
		activityDrawInstructions.put(ManualTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawManualTask(flowNode.getName(),
								graphicInfo, scaleFactor);
					}
				});

		// businessRuleTask task
		activityDrawInstructions.put(BusinessRuleTask.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawBusinessRuleTask(
								flowNode.getName(), graphicInfo, scaleFactor);
					}
				});

		// exclusive gateway
		activityDrawInstructions.put(ExclusiveGateway.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawExclusiveGateway(
								flowNode.getName(), graphicInfo, scaleFactor);
					}
				});

		// inclusive gateway
		activityDrawInstructions.put(InclusiveGateway.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawInclusiveGateway(
								flowNode.getName(), graphicInfo, scaleFactor);
					}
				});

		// parallel gateway
		activityDrawInstructions.put(ParallelGateway.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawParallelGateway(
								flowNode.getName(), graphicInfo, scaleFactor);
					}
				});

		// event based gateway
		activityDrawInstructions.put(EventGateway.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawEventBasedGateway(graphicInfo,
								scaleFactor);
					}
				});

		// Boundary timer
		activityDrawInstructions.put(BoundaryEvent.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode;
						if (boundaryEvent.getEventDefinitions() != null
								&& boundaryEvent.getEventDefinitions().size() > 0) {
							if (boundaryEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) {

								processDiagramCanvas.drawCatchingTimerEvent(
										flowNode.getName(), graphicInfo,
										boundaryEvent.isCancelActivity(),
										scaleFactor);

							} else if (boundaryEvent.getEventDefinitions().get(
									0) instanceof ErrorEventDefinition) {

								processDiagramCanvas.drawCatchingErrorEvent(
										graphicInfo,
										boundaryEvent.isCancelActivity(),
										scaleFactor);

							} else if (boundaryEvent.getEventDefinitions().get(
									0) instanceof SignalEventDefinition) {
								processDiagramCanvas.drawCatchingSignalEvent(
										flowNode.getName(), graphicInfo,
										boundaryEvent.isCancelActivity(),
										scaleFactor);
							}
						}

					}
				});

		// subprocess
		activityDrawInstructions.put(SubProcess.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						if (graphicInfo.getExpanded() != null
								&& !graphicInfo.getExpanded()) {
							processDiagramCanvas.drawCollapsedSubProcess(
									flowNode.getName(), graphicInfo, false);
						} else {
							processDiagramCanvas.drawExpandedSubProcess(
									flowNode.getName(), graphicInfo, false,
									scaleFactor);
						}
					}
				});

		// Event subprocess
		activityDrawInstructions.put(EventSubProcess.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						if (graphicInfo.getExpanded() != null
								&& !graphicInfo.getExpanded()) {
							processDiagramCanvas.drawCollapsedSubProcess(
									flowNode.getName(), graphicInfo, true);
						} else {
							processDiagramCanvas.drawExpandedSubProcess(
									flowNode.getName(), graphicInfo, true,
									scaleFactor);
						}
					}
				});

		// call activity
		activityDrawInstructions.put(CallActivity.class,
				new ActivityDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, FlowNode flowNode) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(flowNode.getId());
						processDiagramCanvas.drawCollapsedCallActivity(
								flowNode.getName(), graphicInfo);
					}
				});

		// text annotation
		artifactDrawInstructions.put(TextAnnotation.class,
				new ArtifactDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, Artifact artifact) {
						GraphicInfo graphicInfo = bpmnModel
								.getGraphicInfo(artifact.getId());
						TextAnnotation textAnnotation = (TextAnnotation) artifact;
						processDiagramCanvas.drawTextAnnotation(
								textAnnotation.getText(), graphicInfo);
					}
				});

		// association
		artifactDrawInstructions.put(Association.class,
				new ArtifactDrawInstruction() {

					public void draw(
							CustomerProcessDiagramCanvas processDiagramCanvas,
							BpmnModel bpmnModel, Artifact artifact) {
						Association association = (Association) artifact;
						String sourceRef = association.getSourceRef();
						String targetRef = association.getTargetRef();

						// source and target can be instance of FlowElement or
						// Artifact
						BaseElement sourceElement = bpmnModel
								.getFlowElement(sourceRef);
						BaseElement targetElement = bpmnModel
								.getFlowElement(targetRef);
						if (sourceElement == null) {
							sourceElement = bpmnModel.getArtifact(sourceRef);
						}
						if (targetElement == null) {
							targetElement = bpmnModel.getArtifact(targetRef);
						}
						List<GraphicInfo> graphicInfoList = bpmnModel
								.getFlowLocationGraphicInfo(artifact.getId());
						graphicInfoList = connectionPerfectionizer(
								processDiagramCanvas, bpmnModel, sourceElement,
								targetElement, graphicInfoList);
						int xPoints[] = new int[graphicInfoList.size()];
						int yPoints[] = new int[graphicInfoList.size()];
						for (int i = 1; i < graphicInfoList.size(); i++) {
							GraphicInfo graphicInfo = graphicInfoList.get(i);
							GraphicInfo previousGraphicInfo = graphicInfoList
									.get(i - 1);

							if (i == 1) {
								xPoints[0] = (int) previousGraphicInfo.getX();
								yPoints[0] = (int) previousGraphicInfo.getY();
							}
							xPoints[i] = (int) graphicInfo.getX();
							yPoints[i] = (int) graphicInfo.getY();
						}

						AssociationDirection associationDirection = association
								.getAssociationDirection();
						processDiagramCanvas.drawAssociation(xPoints, yPoints,
								associationDirection, false, scaleFactor);
					}
				});
	}

	protected final Map<Class<? extends BaseElement>, ActivityDrawInstruction> activityDrawInstructions = new HashMap<Class<? extends BaseElement>, ActivityDrawInstruction>();
	protected final Map<Class<? extends BaseElement>, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap<Class<? extends BaseElement>, ArtifactDrawInstruction>();

	public InputStream generateDiagram(BpmnModel bpmnModel,
			List<String> highLightedActivities, List<String> highLightedFlows,
			String currentActivityId) {
		return generateDiagram(bpmnModel, "png", highLightedActivities,
				highLightedFlows, currentActivityId, "宋体", "宋体", null, 1.0d);
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows,
			String currentActivityId, String activityFontName,
			String labelFontName, ClassLoader customClassLoader,
			double scaleFactor) {

		return generateProcessDiagram(bpmnModel, imageType,
				highLightedActivities, highLightedFlows, currentActivityId,
				activityFontName, labelFontName, customClassLoader, scaleFactor)
				.generateImage(imageType);
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows) {
		return generateDiagram(bpmnModel, imageType, highLightedActivities,
				highLightedFlows, null, null, null, null, 1.0);
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows,
			double scaleFactor) {
		return generateDiagram(bpmnModel, imageType, highLightedActivities,
				highLightedFlows, null, null, null, null, scaleFactor);
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities) {
		return generateDiagram(bpmnModel, imageType, highLightedActivities,
				Collections.<String> emptyList());
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, double scaleFactor) {
		return generateDiagram(bpmnModel, imageType, highLightedActivities,
				Collections.<String> emptyList(), scaleFactor);
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			String activityFontName, String labelFontName,
			ClassLoader customClassLoader) {
		return generateDiagram(bpmnModel, imageType,
				Collections.<String> emptyList(),
				Collections.<String> emptyList(), null, activityFontName,
				labelFontName, customClassLoader, 1.0);
	}

	public InputStream generateDiagram(BpmnModel bpmnModel, String imageType,
			String activityFontName, String labelFontName,
			ClassLoader customClassLoader, double scaleFactor) {

		return generateDiagram(bpmnModel, imageType,
				Collections.<String> emptyList(),
				Collections.<String> emptyList(), null, activityFontName,
				labelFontName, customClassLoader, scaleFactor);
	}

	public InputStream generatePngDiagram(BpmnModel bpmnModel) {
		return generatePngDiagram(bpmnModel, 1.0);
	}

	public InputStream generatePngDiagram(BpmnModel bpmnModel,
			double scaleFactor) {
		return generateDiagram(bpmnModel, "png",
				Collections.<String> emptyList(),
				Collections.<String> emptyList(), scaleFactor);
	}

	public InputStream generateJpgDiagram(BpmnModel bpmnModel) {
		return generateJpgDiagram(bpmnModel, 1.0);
	}

	public InputStream generateJpgDiagram(BpmnModel bpmnModel,
			double scaleFactor) {
		return generateDiagram(bpmnModel, "jpg",
				Collections.<String> emptyList(),
				Collections.<String> emptyList());
	}

	public BufferedImage generateImage(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows,
			String currentActivityId, String activityFontName,
			String labelFontName, ClassLoader customClassLoader,
			double scaleFactor) {

		return generateProcessDiagram(bpmnModel, imageType,
				highLightedActivities, highLightedFlows, currentActivityId,
				activityFontName, labelFontName, customClassLoader, scaleFactor)
				.generateBufferedImage(imageType);
	}

	public BufferedImage generateImage(BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows,
			String currentActivityId, double scaleFactor) {

		return generateImage(bpmnModel, imageType, highLightedActivities,
				highLightedFlows, currentActivityId, null, null, null,
				scaleFactor);
	}

	public BufferedImage generatePngImage(BpmnModel bpmnModel,
			double scaleFactor) {
		return generateImage(bpmnModel, "png",
				Collections.<String> emptyList(),
				Collections.<String> emptyList(), null, scaleFactor);
	}

	protected CustomerProcessDiagramCanvas generateProcessDiagram(
			BpmnModel bpmnModel, String imageType,
			List<String> highLightedActivities, List<String> highLightedFlows,
			String currentActivityId, String activityFontName,
			String labelFontName, ClassLoader customClassLoader,
			double scaleFactor) {

		CustomerProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(
				bpmnModel, imageType, activityFontName, labelFontName,
				customClassLoader);

		// Draw pool shape, if process is participant in collaboration
		for (Pool pool : bpmnModel.getPools()) {
			GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
			processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo);
		}

		// Draw lanes
		for (Process process : bpmnModel.getProcesses()) {
			for (Lane lane : process.getLanes()) {
				GraphicInfo graphicInfo = bpmnModel
						.getGraphicInfo(lane.getId());
				processDiagramCanvas
						.drawPoolOrLane(lane.getName(), graphicInfo);
			}
		}

		// Draw activities and their sequence-flows
		for (FlowNode flowNode : bpmnModel.getProcesses().get(0)
				.findFlowElementsOfType(FlowNode.class)) {
			drawActivity(processDiagramCanvas, bpmnModel, flowNode,
					highLightedActivities, highLightedFlows, currentActivityId,
					scaleFactor);
		}

		// Draw artifacts
		for (Process process : bpmnModel.getProcesses()) {
			for (Artifact artifact : process.getArtifacts()) {
				drawArtifact(processDiagramCanvas, bpmnModel, artifact);
			}
		}

		return processDiagramCanvas;
	}

	protected void drawActivity(
			CustomerProcessDiagramCanvas processDiagramCanvas,
			BpmnModel bpmnModel, FlowNode flowNode,
			List<String> highLightedActivities, List<String> highLightedFlows,
			String currentActivityId, double scaleFactor) {

		ActivityDrawInstruction drawInstruction = activityDrawInstructions
				.get(flowNode.getClass());
		if (drawInstruction != null) {

			drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode);

			// Gather info on the multi instance marker
			boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false;
			if (flowNode instanceof Activity) {
				Activity activity = (Activity) flowNode;
				MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity
						.getLoopCharacteristics();
				if (multiInstanceLoopCharacteristics != null) {
					multiInstanceSequential = multiInstanceLoopCharacteristics
							.isSequential();
					multiInstanceParallel = !multiInstanceSequential;
				}
			}

			// Gather info on the collapsed marker
			GraphicInfo graphicInfo = bpmnModel
					.getGraphicInfo(flowNode.getId());
			if (flowNode instanceof SubProcess) {
				collapsed = graphicInfo.getExpanded() != null
						&& !graphicInfo.getExpanded();
			} else if (flowNode instanceof CallActivity) {
				collapsed = true;
			}

			if (scaleFactor == 1.0) {
				// Actually draw the markers
				processDiagramCanvas.drawActivityMarkers(
						(int) graphicInfo.getX(), (int) graphicInfo.getY(),
						(int) graphicInfo.getWidth(),
						(int) graphicInfo.getHeight(), multiInstanceSequential,
						multiInstanceParallel, collapsed);
			}

			// Draw highlighted activities
			if (highLightedActivities.contains(flowNode.getId())) {
				int arc = 10;
				if (flowNode instanceof StartEvent
						|| flowNode instanceof EndEvent) {
					arc = 20;
				}

				if (flowNode.getId().equals(currentActivityId)) {
					drawHighLightBlue(processDiagramCanvas,
							bpmnModel.getGraphicInfo(flowNode.getId()), arc);
				} else {

					drawHighLight(processDiagramCanvas,
							bpmnModel.getGraphicInfo(flowNode.getId()), arc);
				}
			}

		}

		// Outgoing transitions of activity
		for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
			boolean highLighted = (highLightedFlows.contains(sequenceFlow
					.getId()));
			String defaultFlow = null;
			String name = sequenceFlow.getName();

			if (flowNode instanceof Activity) {
				defaultFlow = ((Activity) flowNode).getDefaultFlow();
			} else if (flowNode instanceof Gateway) {
				defaultFlow = ((Gateway) flowNode).getDefaultFlow();
			}

			boolean isDefault = false;
			if (defaultFlow != null
					&& defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) {
				isDefault = true;
			}
			boolean drawConditionalIndicator = sequenceFlow
					.getConditionExpression() != null
					&& !(flowNode instanceof Gateway);

			String sourceRef = sequenceFlow.getSourceRef();
			String targetRef = sequenceFlow.getTargetRef();
			FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef);
			FlowElement targetElement = bpmnModel.getFlowElement(targetRef);
			List<GraphicInfo> graphicInfoList = bpmnModel
					.getFlowLocationGraphicInfo(sequenceFlow.getId());
			graphicInfoList = connectionPerfectionizer(processDiagramCanvas,
					bpmnModel, sourceElement, targetElement, graphicInfoList);
			int xPoints[] = new int[graphicInfoList.size()];
			int yPoints[] = new int[graphicInfoList.size()];

			for (int i = 1; i < graphicInfoList.size(); i++) {
				GraphicInfo graphicInfo = graphicInfoList.get(i);
				GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);

				if (i == 1) {
					xPoints[0] = (int) previousGraphicInfo.getX();
					yPoints[0] = (int) previousGraphicInfo.getY();
				}
				xPoints[i] = (int) graphicInfo.getX();
				yPoints[i] = (int) graphicInfo.getY();

			}

			processDiagramCanvas.drawSequenceflow(name, xPoints, yPoints,
					drawConditionalIndicator, isDefault, highLighted,
					scaleFactor);

			// Draw sequenceflow label
			GraphicInfo labelGraphicInfo = bpmnModel
					.getLabelGraphicInfo(sequenceFlow.getId());
			if (labelGraphicInfo != null) {
				GraphicInfo lineCenter = getLineCenter(graphicInfoList);
				processDiagramCanvas.drawLabel(sequenceFlow.getName(),
						lineCenter, false);
			}
		}

		// Nested elements
		if (flowNode instanceof FlowElementsContainer) {
			for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode)
					.getFlowElements()) {
				if (nestedFlowElement instanceof FlowNode) {
					drawActivity(processDiagramCanvas, bpmnModel,
							(FlowNode) nestedFlowElement,
							highLightedActivities, highLightedFlows,
							currentActivityId, scaleFactor);
				}
			}
		}
	}

	/**
	 * This method makes coordinates of connection flow better.
	 * 
	 * @param processDiagramCanvas
	 * @param bpmnModel
	 * @param sourceElement
	 * @param targetElement
	 * @param graphicInfoList
	 * @return
	 */
	protected static List<GraphicInfo> connectionPerfectionizer(
			CustomerProcessDiagramCanvas processDiagramCanvas,
			BpmnModel bpmnModel, BaseElement sourceElement,
			BaseElement targetElement, List<GraphicInfo> graphicInfoList) {
		GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement
				.getId());
		GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement
				.getId());

		CustomerProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement);
		CustomerProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement);

		return processDiagramCanvas.connectionPerfectionizer(sourceShapeType,
				targetShapeType, sourceGraphicInfo, targetGraphicInfo,
				graphicInfoList);
	}

	/**
	 * This method returns shape type of base element.<br>
	 * Each element can be presented as rectangle, arhombus, or ellipse.
	 * 
	 * @param baseElement
	 * @return CustomerProcessDiagramCanvas.SHAPE_TYPE
	 */
	protected static CustomerProcessDiagramCanvas.SHAPE_TYPE getShapeType(
			BaseElement baseElement) {
		if (baseElement instanceof Task || baseElement instanceof Activity
				|| baseElement instanceof TextAnnotation) {
			return CustomerProcessDiagramCanvas.SHAPE_TYPE.Rectangle;
		} else if (baseElement instanceof Gateway) {
			return CustomerProcessDiagramCanvas.SHAPE_TYPE.Rhombus;
		} else if (baseElement instanceof Event) {
			return CustomerProcessDiagramCanvas.SHAPE_TYPE.Ellipse;
		} else {
			// unknown source element, just do not correct coordinates
		}
		return null;
	}

	protected static GraphicInfo getLineCenter(List<GraphicInfo> graphicInfoList) {
		GraphicInfo gi = new GraphicInfo();

		int xPoints[] = new int[graphicInfoList.size()];
		int yPoints[] = new int[graphicInfoList.size()];

		double length = 0;
		double[] lengths = new double[graphicInfoList.size()];
		lengths[0] = 0;
		double m;
		for (int i = 1; i < graphicInfoList.size(); i++) {
			GraphicInfo graphicInfo = graphicInfoList.get(i);
			GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1);

			if (i == 1) {
				xPoints[0] = (int) previousGraphicInfo.getX();
				yPoints[0] = (int) previousGraphicInfo.getY();
			}
			xPoints[i] = (int) graphicInfo.getX();
			yPoints[i] = (int) graphicInfo.getY();

			length += Math.sqrt(Math.pow((int) graphicInfo.getX()
					- (int) previousGraphicInfo.getX(), 2)
					+ Math.pow((int) graphicInfo.getY()
							- (int) previousGraphicInfo.getY(), 2));
			lengths[i] = length;
		}
		m = length / 2;
		int p1 = 0, p2 = 1;
		for (int i = 1; i < lengths.length; i++) {
			double len = lengths[i];
			p1 = i - 1;
			p2 = i;
			if (len > m) {
				break;
			}
		}

		GraphicInfo graphicInfo1 = graphicInfoList.get(p1);
		GraphicInfo graphicInfo2 = graphicInfoList.get(p2);

		double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX();
		double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY();
		double OB = lengths[p2] - lengths[p1];
		double ob = m - lengths[p1];
		double ab = AB * ob / OB;
		double oa = OA * ob / OB;

		double mx = graphicInfo1.getX() + ab;
		double my = graphicInfo1.getY() + oa;

		gi.setX(mx);
		gi.setY(my);
		return gi;
	}

	protected void drawArtifact(
			CustomerProcessDiagramCanvas processDiagramCanvas,
			BpmnModel bpmnModel, Artifact artifact) {

		ArtifactDrawInstruction drawInstruction = artifactDrawInstructions
				.get(artifact.getClass());
		if (drawInstruction != null) {
			drawInstruction.draw(processDiagramCanvas, bpmnModel, artifact);
		}
	}

	private static void drawHighLightBlue(
			CustomerProcessDiagramCanvas processDiagramCanvas,
			GraphicInfo graphicInfo, int arc) {
		processDiagramCanvas.drawHighLightBlue((int) graphicInfo.getX(),
				(int) graphicInfo.getY(), (int) graphicInfo.getWidth(),
				(int) graphicInfo.getHeight(), arc);

	}

	private static void drawHighLight(
			CustomerProcessDiagramCanvas processDiagramCanvas,
			GraphicInfo graphicInfo, int arc) {
		processDiagramCanvas.drawHighLight((int) graphicInfo.getX(),
				(int) graphicInfo.getY(), (int) graphicInfo.getWidth(),
				(int) graphicInfo.getHeight(), arc);

	}

	protected static CustomerProcessDiagramCanvas initProcessDiagramCanvas(
			BpmnModel bpmnModel, String imageType, String activityFontName,
			String labelFontName, ClassLoader customClassLoader) {

		// We need to calculate maximum values to know how big the image will be
		// in its entirety
		double minX = Double.MAX_VALUE;
		double maxX = 0;
		double minY = Double.MAX_VALUE;
		double maxY = 0;

		for (Pool pool : bpmnModel.getPools()) {
			GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId());
			minX = graphicInfo.getX();
			maxX = graphicInfo.getX() + graphicInfo.getWidth();
			minY = graphicInfo.getY();
			maxY = graphicInfo.getY() + graphicInfo.getHeight();
		}

		List<FlowNode> flowNodes = gatherAllFlowNodes(bpmnModel);
		for (FlowNode flowNode : flowNodes) {

			GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode
					.getId());

			// width
			if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) {
				maxX = flowNodeGraphicInfo.getX()
						+ flowNodeGraphicInfo.getWidth();
			}
			if (flowNodeGraphicInfo.getX() < minX) {
				minX = flowNodeGraphicInfo.getX();
			}
			// height
			if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) {
				maxY = flowNodeGraphicInfo.getY()
						+ flowNodeGraphicInfo.getHeight();
			}
			if (flowNodeGraphicInfo.getY() < minY) {
				minY = flowNodeGraphicInfo.getY();
			}

			for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) {
				List<GraphicInfo> graphicInfoList = bpmnModel
						.getFlowLocationGraphicInfo(sequenceFlow.getId());

				for (GraphicInfo graphicInfo : graphicInfoList) {
					// width
					if (graphicInfo.getX() > maxX) {
						maxX = graphicInfo.getX();
					}
					if (graphicInfo.getX() < minX) {
						minX = graphicInfo.getX();
					}
					// height
					if (graphicInfo.getY() > maxY) {
						maxY = graphicInfo.getY();
					}
					if (graphicInfo.getY() < minY) {
						minY = graphicInfo.getY();
					}
				}
			}
		}

		List<Artifact> artifacts = gatherAllArtifacts(bpmnModel);
		for (Artifact artifact : artifacts) {

			GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact
					.getId());

			if (artifactGraphicInfo != null) {
				// width
				if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) {
					maxX = artifactGraphicInfo.getX()
							+ artifactGraphicInfo.getWidth();
				}
				if (artifactGraphicInfo.getX() < minX) {
					minX = artifactGraphicInfo.getX();
				}
				// height
				if (artifactGraphicInfo.getY()
						+ artifactGraphicInfo.getHeight() > maxY) {
					maxY = artifactGraphicInfo.getY()
							+ artifactGraphicInfo.getHeight();
				}
				if (artifactGraphicInfo.getY() < minY) {
					minY = artifactGraphicInfo.getY();
				}
			}

			List<GraphicInfo> graphicInfoList = bpmnModel
					.getFlowLocationGraphicInfo(artifact.getId());
			if (graphicInfoList != null) {
				for (GraphicInfo graphicInfo : graphicInfoList) {
					// width
					if (graphicInfo.getX() > maxX) {
						maxX = graphicInfo.getX();
					}
					if (graphicInfo.getX() < minX) {
						minX = graphicInfo.getX();
					}
					// height
					if (graphicInfo.getY() > maxY) {
						maxY = graphicInfo.getY();
					}
					if (graphicInfo.getY() < minY) {
						minY = graphicInfo.getY();
					}
				}
			}
		}

		int nrOfLanes = 0;
		for (Process process : bpmnModel.getProcesses()) {
			for (Lane l : process.getLanes()) {

				nrOfLanes++;

				GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId());
				// // width
				if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) {
					maxX = graphicInfo.getX() + graphicInfo.getWidth();
				}
				if (graphicInfo.getX() < minX) {
					minX = graphicInfo.getX();
				}
				// height
				if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) {
					maxY = graphicInfo.getY() + graphicInfo.getHeight();
				}
				if (graphicInfo.getY() < minY) {
					minY = graphicInfo.getY();
				}
			}
		}

		// Special case, see http://jira.codehaus.org/browse/ACT-1431
		if (flowNodes.size() == 0 && bpmnModel.getPools().size() == 0
				&& nrOfLanes == 0) {
			// Nothing to show
			minX = 0;
			minY = 0;
		}

		return new CustomerProcessDiagramCanvas((int) maxX + 10,
				(int) maxY + 10, (int) minX, (int) minY, imageType,
				activityFontName, labelFontName, customClassLoader);
	}

	protected static List<Artifact> gatherAllArtifacts(BpmnModel bpmnModel) {
		List<Artifact> artifacts = new ArrayList<Artifact>();
		for (Process process : bpmnModel.getProcesses()) {
			artifacts.addAll(process.getArtifacts());
		}
		return artifacts;
	}

	protected static List<FlowNode> gatherAllFlowNodes(BpmnModel bpmnModel) {
		List<FlowNode> flowNodes = new ArrayList<FlowNode>();
		for (Process process : bpmnModel.getProcesses()) {
			flowNodes.addAll(gatherAllFlowNodes(process));
		}
		return flowNodes;
	}

	protected static List<FlowNode> gatherAllFlowNodes(
			FlowElementsContainer flowElementsContainer) {
		List<FlowNode> flowNodes = new ArrayList<FlowNode>();
		for (FlowElement flowElement : flowElementsContainer.getFlowElements()) {
			if (flowElement instanceof FlowNode) {
				flowNodes.add((FlowNode) flowElement);
			}
			if (flowElement instanceof FlowElementsContainer) {
				flowNodes
						.addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement));
			}
		}
		return flowNodes;
	}

	protected interface ActivityDrawInstruction {
		void draw(CustomerProcessDiagramCanvas processDiagramCanvas,
				BpmnModel bpmnModel, FlowNode flowNode);
	}

	protected interface ArtifactDrawInstruction {
		void draw(CustomerProcessDiagramCanvas processDiagramCanvas,
				BpmnModel bpmnModel, Artifact artifact);
	}

}